/*****************************************************************************
 * $LastChangedDate: 2011-09-10 23:54:34 -0400 (Sat, 10 Sep 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Math test.
 *//*
 * LEGAL:   COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define TESTING 1
#include "test.hh"
#include "base/module.hh"
using namespace base;
#include "math/module.hh"
#include "math/defs_trig.hh"
#include "math/defs_vector.hh"
#include "math/types_trig.hh"
#include "math/vertex.hh"
#include "math/funcs.hh"
#include "math/funcs_vector.hh"
using namespace math;

/*****************************************************************************
 * This asm code in C++ template returning args by reference looks suspicious,
 * so it's tested with different float types.
 *****************************************************************************/
template<typename FP>
void
SinCos_( Radian rad, FP& si, FP& co )
{
#if !defined(__clang__) && defined(__GNUC__) && CPU_X86
    asm("fsincos" : "=t" (co), "=u" (si) : "0" (rad));
#else
    si = std::sin( rad );
    co = std::cos( rad );
#endif
}

bool
CheckSinCos( fp rad )
{
#if CPU_X86
    errno = 0;
 {
    fp si, co;
    SinCos_( rad, si, co );
    fp si2, co2;
    si2 = sin( rad );
    co2 = cos( rad );
    if ( not FP_EQ(si,si2) ) return false;
    if ( not FP_EQ(co,co2) ) return false;
 }

 {
    fpx si, co;
    SinCos_( rad, si, co );
    fpx si2, co2;
    si2 = sin( rad );
    co2 = cos( rad );
    if ( not FP_EQ(si,si2) ) return false;
    if ( not FP_EQ(co,co2) ) return false;
 }

 {
    long double si, co;
    SinCos_( rad, si, co );
    long double si2, co2;
    si2 = sin( rad );
    co2 = cos( rad );
    if ( not FP_EQ(si,si2) ) return false;
    if ( not FP_EQ(co,co2) ) return false;
 }

    return errno == 0;
#else
    return true;
#endif
}

/*****************************************************************************
 * Test SinCos().
 *****************************************************************************/
bool
TestSinCos( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing SinCos() inline assembly. \n";
    CDEBUG << "============================================\n";

    if ( ! CheckSinCos( 0.0 ) ) goto Error;
    if ( ! CheckSinCos( 1.0 ) ) goto Error;
    if ( ! CheckSinCos( -1.0 ) ) goto Error;
    if ( ! CheckSinCos( -0.5 ) ) goto Error;
    if ( ! CheckSinCos( math::PI ) ) goto Error;
    if ( ! CheckSinCos( math::PI_DOUBLE ) ) goto Error;
    if ( ! CheckSinCos( -math::PI ) ) goto Error;
    if ( ! CheckSinCos( -math::PI_DOUBLE ) ) goto Error;
    if ( ! CheckSinCos( math::PI * 2.0 ) ) goto Error;
    if ( ! CheckSinCos( math::PI_DOUBLE * 2.0 ) ) goto Error;

    return true;

Error:
    return false;
}

/*****************************************************************************
 * Test FP_EQ(a,b).
 *****************************************************************************/
bool
TestFP_EQ( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing FP_EQ(a,b). \n";
    CDEBUG << "============================================\n";

 {
    if ( not FP_EQ( 100.0, 100.00001 ) ) return Fail();
    if (     FP_EQ( 100.0, 100.1 ) ) return Fail();
 }

 {
    fp a = 1.2345f;
    fp b = 1.2345f;
    if ( ! FP_EQ(a,b)   ) return Fail();
 }

 {
    fpx a = 1.2345;
    fpx b = 1.2345;
    if ( ! FP_EQ(a,b) ) return Fail();
 }

 {
    fp a = 1.22f;
    fp b = 1.23f;
    if (   FP_EQ(a,b) ) return Fail();  // compares 3 fractional digits
 }

 {
    fp a = 1.22f;
    fp b = 2.22f;
    if ( FP_EQ(a,b) ) return Fail();
 }

 {
    fp a = -3.21f;
    fp b =  3.21f;
    if ( FP_EQ(a,b) ) return Fail();
 }

 {
    fp a = 0.999999999f;  // near limit of fp precision
    fp b = 1.000000000f;
    if ( ! FP_EQ(a,b) ) return Fail();
 }

 {
    fpx a = 0.999999999;
    fpx b = 1.000000000;
    if ( ! FP_EQ(a,b) ) return Fail();
 }

 {
    fp a =  99999.9999f; // near limit of fp precision
    fp b = 100000.0000f;
    if ( ! FP_EQ(a,b) ) return Fail();
 }

 {
    fpx a =  99999.9999;
    fpx b = 100000.0000;
    if ( ! FP_EQ(a,b) ) return Fail();
 }

 {
    fpx a = -0.999999999;  // negative
    fpx b = 1.000000000;
    if ( FP_EQ(a,b) ) return Fail();
 }

 {
    fpx a =   999.999;
    fpx b = -1000.000;  // negative
    if ( FP_EQ(a,b) ) return Fail();
 }

    return true;
}

/*****************************************************************************
 * [ FP_EQ(a,b,fractionalDigits) is tested in math_deprecated.cc ]
 *****************************************************************************/

/*****************************************************************************
 * Test FP_LE().
 *****************************************************************************/
bool
TestFP_LE( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing FP_LE(). \n";
    CDEBUG << "============================================\n";

 {
    if ( ! FP_LE<fp>( 10.0f, 20.0f ) ) return Fail();
    if ( ! FP_LE<fp>( 0.9f, 1.0f ) ) return Fail();
    if (   FP_LE<fp>( 1.1f, 1.0f ) ) return Fail();
    if ( ! FP_LE<fp>( 1.1f, 1.1f ) ) return Fail();
    if ( ! FP_LE<fp>( 1.09f, 1.1f ) ) return Fail();
    if ( ! FP_LE<fp>( -0.0000001f, 0.0f ) ) return Fail();  // close to equal
    if ( ! FP_LE<fp>( 1.000001f, 1.0f ) ) return Fail();  // close to equal
    if ( ! FP_LE<fpx>( 99.5, 99.500001 ) ) return Fail();  // close to equal
    if ( ! FP_LE<fpx>( 99.5, 99.499999 ) ) return Fail();  // close to equal
 }

    return true;
}

/*****************************************************************************
 * Test FP_GE().
 *****************************************************************************/
bool
TestFP_GE( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing FP_GE(). \n";
    CDEBUG << "============================================\n";

 {
    if ( ! FP_GE<fp>( 20.0f, 10.0f ) ) return Fail();
    if ( ! FP_GE<fp>( 1.0f, 1.0f ) ) return Fail();
    if (   FP_GE<fp>( 1.0f, 2.0f ) ) return Fail();  // less
    if ( ! FP_GE<fp>( 5.0f, 1.0f ) ) return Fail();
    if ( ! FP_GE<fp>( -0.000001f, 0.0f ) ) return Fail();  // close to equal
    if ( ! FP_GE<fp>( 1.000001f, 1.0f ) ) return Fail();  // close to equal
    if ( ! FP_GE<fpx>( 99.5, 99.500001 ) ) return Fail();  // close to equal
    if ( ! FP_GE<fpx>( 99.5, 99.499999 ) ) return Fail();  // close to equal
 }

    return true;
}

/*****************************************************************************
 * Test IfInRange().
 *****************************************************************************/
bool
TestIfInRange( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing IfInRange(). \n";
    CDEBUG << "============================================\n";

    if ( not IfInRange<int>( 10,20, 10,20 ) ) { return Fail(); }
    if ( not IfInRange<fp>( 10,20, 11,19 ) ) { return Fail(); }
    if ( not IfInRange<fp>( 10,20, 9,10 ) ) { return Fail(); }
    if ( not IfInRange<fp>( 10,20, -10,30 ) ) { return Fail(); }
    if ( not IfInRange<fp>( 10,20, 10,10 ) ) { return Fail(); }
    if ( not IfInRange<fp>( 10,20, 20,20 ) ) { return Fail(); }

    if ( IfInRange<fp>( 10,20, 0,1 ) ) { return Fail(); }
    if ( IfInRange<fp>( 10,20, 9,9 ) ) { return Fail(); }
    if ( IfInRange<fp>( 10,20, 21,99 ) ) { return Fail(); }
    if ( IfInRange<fp>( 10,20, -10,-20 ) ) { return Fail(); }

    return true;
}

/*****************************************************************************
 * Test IfNan().
 *****************************************************************************/
bool
TestIfNan( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing IfNan(). \n";
    CDEBUG << "============================================\n";

    volatile fp f = 0.0f;
    volatile fp quotient = f / f;
    if ( not IfNan(quotient) ) { return Fail(); }
    if ( not IfNan( acos(999.0f) ) ) { return Fail(); }
    if ( IfNan(f) ) { return Fail(); }
    if ( IfNan(0.0f) ) { return Fail(); }

    return true;
}

/*****************************************************************************
 * Test Distance().
 *****************************************************************************/
bool
TestDistance( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing Distance(). \n";
    CDEBUG << "============================================\n";

 {
    fp dist = Distance( Vector3( 1.0f, 0.0f, 0.0f ) );
    if ( not FP_EQ( dist, 1.0f ) ) { return Fail(); }
 }

 {
    fp dist = Distance( Vector3( 2.0f, 2.0f, 2.0f ) );
    const fp dist2 = SquareRoot( 4.0f + 4.0f + 4.0f );
    if ( not FP_EQ( dist, dist2 ) ) { return Fail(); }
 }

    return true;
}

/*****************************************************************************
 * Test Normalize().
 *****************************************************************************/
bool
TestNormalize( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing Normalize(). \n";
    CDEBUG << "============================================\n";

 {
    const Vector3 v( 2.0f, 4.0f, -8.0f );
    const Vector3 n = Normalize( v );
    const fp      d = Distance( n );
    if ( not FP_EQ( d, 1.0f ) ) { return Fail(); }
 }

    return true;
}

/*****************************************************************************
 * Test CrossProduct() which makes a normal vector.
 *****************************************************************************/
bool
TestCrossProduct( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing CrossProduct(). \n";
    CDEBUG << "============================================\n";

 {
    const Vector3 v0( 0.0f, 0.0f, 0.0f );  // origin
    const Vector3 v1( 1.0f, 0.0f, 0.0f );  // X
    const Vector3 v2( 0.0f, 1.0f, 0.0f );  // Y
    const Vector3 normal = CrossProduct( v1, v2, v0 );
    if ( (not FP_EQ( normal[XX], 0.0f ))
      or (not FP_EQ( normal[YY], 0.0f ))
      or (not FP_EQ( normal[ZZ], 1.0f )) )  // Z is normal to XY plane
        return Fail();
 }

 {
    const Vector3 v0( 0.0f, 0.0f, 0.0f );  // origin
    const Vector3 v1( 0.0f, 1.0f, 0.0f );  // Y
    const Vector3 v2( 0.0f, 0.0f, 1.0f );  // Z
    const Vector3 normal = CrossProduct( v1, v2, v0 );
    if ( (not FP_EQ( normal[XX], 1.0f ))
      or (not FP_EQ( normal[YY], 0.0f ))
      or (not FP_EQ( normal[ZZ], 0.0f )) )  // X is normal to YZ plane
        return Fail();
 }

    return true;
}

/*****************************************************************************
 * Test Angle3().
 * Unlike a vector, an angle has no orientation nor winding direction.
 * The angle will be the same if one of the vectors is turned in the opposite direction.
 * The measured angle will never be greater than 180 degrees.
 *****************************************************************************/
bool
TestAngle3( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing Angle3(). \n";
    CDEBUG << "============================================\n";

    // Test 90 deg.
    // In these first two cases, angle result remains positive.
 {
    const Vector3 v0( 1.0f, 0.0f, 0.0f );
    const Vector3 v1( 0.0f, 1.0f, 0.0f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), math::RADIAN_90.FP()) ) { return Fail(); }
 }

 {
    const Vector3 v0( 1.0f, 0.0f, 0.0f );
    const Vector3 v1( 0.0f, -1.0f, 0.0f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), math::RADIAN_90.FP()) ) { return Fail(); }
 }

    // Test 180 deg.
 {
    const Vector3 v0( 1.0f, 0.0f, 0.0f );
    const Vector3 v1( -1.0f, 0.0f, 0.0f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), math::RADIAN_180.FP() ) ) { return Fail(); }
 }
 {
    const Vector3 v0( 0.0f,  0.5f,  0.5f );
    const Vector3 v1( 0.0f, -0.5f, -0.5f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), math::RADIAN_180.FP() ) ) { return Fail(); }
 }

    // Prove that an measured angle should never be greater than 180 degrees.
    // Test not-quite 180 degrees with one vector,
    // then re-test with that vector but negate a dimension,
    // which should still be slightly less than 180 (not slightly greater).
 {
    const Vector3 v0( 1.0f, 0.0f, 0.0f );
    const Vector3 v1( -1.0f, 0.05f, 0.0f );  // +Y
    const Radian rad = Angle3( v0, v1 );
    // Slightly less than 180 deg.
    if ( not FP_EQ( rad.FP(), Deg2Rad(177.138f).FP() ) ) { return Fail(); }
 }
 {
    const Vector3 v0( 1.0f, 0.0f, 0.0f );
    const Vector3 v1( -1.0f, -0.05f, 0.0f );  // -Y
    const Radian rad = Angle3( v0, v1 );
    // Still slightly less than 180 deg.
    if ( not FP_EQ( rad.FP(), Deg2Rad(177.138f).FP() ) ) { return Fail(); }
 }

    // Test non-unit vectors.
 {
    const Vector3 v0(  1.0f, 0.0f, 0.0f );
    const Vector3 v1( 10.0f, 0.0f, 0.0f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), Radian(0).FP() ) ) { return Fail(); }
 }
 {
    const Vector3 v0( 0.0f,  1.0f,  0.0f );
    const Vector3 v1( 0.0f, 10.0f, 10.0f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), Deg2Rad(45.0f).FP() ) ) { return Fail(); }
 }

    // 54.7 degrees is the 3D counterparts of 45.0 degrees in 2D.
    //
    // sqrt( 1/2 ) = sqrt( 0.5 )   = 0.7071
    // sqrt( 1/3 ) = sqrt( 0.333 ) = 0.5773
    //
    // acos( 0.7071 ) = Rad2Deg( 45.0 )
    // acos( 0.5773 ) = Rad2Deg( 54.7 )
 {
    const Vector3 v0( 1.0f, 0.0f, 0.0f );  // X=1
    const Vector3 v1( 10.0f, 10.0f, 10.0f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), Deg2Rad(54.7356f).FP() ) ) { return Fail(); }
 }
 {
    const Vector3 v0( 0.0f, 1.0f, 0.0f );  // Y=1
    const Vector3 v1( 10.0f, 10.0f, 10.0f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), Deg2Rad(54.7356f).FP() ) ) { return Fail(); }
 }
 {
    const Vector3 v0( 0.0f, 0.0f, 1.0f );  // Z=1
    const Vector3 v1( 10.0f, 10.0f, 10.0f );
    const Radian rad = Angle3( v0, v1 );
    if ( not FP_EQ( rad.FP(), Deg2Rad(54.7356f).FP() ) ) { return Fail(); }
 }

    return true;
}

/*****************************************************************************
 * Test Midpoint().
 *****************************************************************************/
bool
TestMidpoint( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing Midpoint(). \n";
    CDEBUG << "============================================\n";

 {
    const Vector3 v0(  50.0f, 200.0f, -1000.0f );
    const Vector3 v1( 100.0f, 400.0f, -2000.0f );
    const Vector3 mid = Midpoint( v0, v1 );
    if ( not FP_EQ( mid[XX], 75.0f ) ) { return Fail(); }
    if ( not FP_EQ( mid[YY], 300.0f ) ) { return Fail(); }
    if ( not FP_EQ( mid[ZZ], -1500.0f ) ) { return Fail(); }
 }

    return true;
}

/*****************************************************************************
 * Test Interpolate().
 *****************************************************************************/
bool
TestInterpolate( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing Interpolate(). \n";
    CDEBUG << "============================================\n";

 {
    const Vector3 v0(  50.0f, 200.0f, -1000.0f );
    const Vector3 v1( 100.0f, 400.0f, -2000.0f );
  //const Vector3 mid = Midpoint( v0, v1 );
    const Vector3 mid = Interpolate( v0, v1, 0.5f );  // equivalent
    if ( not FP_EQ( mid[XX], 75.0f ) ) { return Fail(); }
    if ( not FP_EQ( mid[YY], 300.0f ) ) { return Fail(); }
    if ( not FP_EQ( mid[ZZ], -1500.0f ) ) { return Fail(); }
 }

 {
    const Vector3 v0( 50.0f, 60.0f, 70.0f );
    const Vector3 v1( 100.0f, 100.0f, 100.0f );
    const Vector3 vi = Interpolate( v0, v1, 0.0f );
    if ( not FP_EQ( vi[XX], 50.0f ) ) { return Fail(); }
    if ( not FP_EQ( vi[YY], 60.0f ) ) { return Fail(); }
    if ( not FP_EQ( vi[ZZ], 70.0f ) ) { return Fail(); }
 }

 {
    const Vector3 v0( 50.0f, 60.0f, 70.0f );
    const Vector3 v1( 100.0f, 110.0f, 120.0f );
    const Vector3 vi = Interpolate( v0, v1, 1.0f );
    if ( not FP_EQ( vi[XX], 100.0f ) ) { return Fail(); }
    if ( not FP_EQ( vi[YY], 110.0f ) ) { return Fail(); }
    if ( not FP_EQ( vi[ZZ], 120.0f ) ) { return Fail(); }
 }

    return true;
}

/*****************************************************************************
 * Test RotateVector3().
 *****************************************************************************/
bool
TestRotateVector3( void )
{
    CDEBUG << "============================================\n";
    CDEBUG << "Testing RotateVector3(). \n";
    CDEBUG << "============================================\n";

    //--------------------------------------------------------------------------
    // Test rotating around XX.
    CDEBUG << "Testing rotating around XX" << std::endl;
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), XX, Deg2Rad(0.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX], 10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY], 20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ], 30.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), XX, Deg2Rad(90.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX],  10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY], -30.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ],  20.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), XX, Deg2Rad(180.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX],  10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY], -20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ], -30.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), XX, Deg2Rad(270.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX],  10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY],  30.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ], -20.0f ) ) { return Fail(); }
 }

    //--------------------------------------------------------------------------
    // Test rotating around YY.
    CDEBUG << "Testing rotating around YY" << std::endl;
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), YY, Deg2Rad(0.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX], 10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY], 20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ], 30.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), YY, Deg2Rad(90.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX], -30.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY],  20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ],  10.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), YY, Deg2Rad(180.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX], -10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY],  20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ], -30.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), YY, Deg2Rad(270.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX],  30.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY],  20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ], -10.0f ) ) { return Fail(); }
 }

    //--------------------------------------------------------------------------
    // Test rotating around ZZ.
    CDEBUG << "Testing rotating around ZZ" << std::endl;
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), ZZ, Deg2Rad(0.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX], 10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY], 20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ], 30.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), ZZ, Deg2Rad(90.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX], -20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY],  10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ],  30.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), ZZ, Deg2Rad(180.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX], -10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY], -20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ],  30.0f ) ) { return Fail(); }
 }
 {
    const Vector3 v = RotateVector3<Vector3>( Vector3( 10.0f, 20.0f, 30.0f ), ZZ, Deg2Rad(270.0f) );
    CDEBUG << "rotated vector " << v << std::endl;
    if ( not FP_EQ( v[XX],  20.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[YY], -10.0f ) ) { return Fail(); }
    if ( not FP_EQ( v[ZZ],  30.0f ) ) { return Fail(); }
 }

    return true;
}

/*****************************************************************************
 * main.
 *****************************************************************************/
int main( int argc, char** argv )
{
    RUN_TEST( "math", TEST_ERROR_MODE_ABORT,
              TestSinCos()
          and TestFP_EQ()
          and TestFP_LE()
          and TestFP_GE()
          and TestIfInRange()
          and TestIfNan()
          and TestDistance()
          and TestNormalize()
          and TestCrossProduct()
          and TestAngle3()
          and TestMidpoint()
          and TestInterpolate()
          and TestRotateVector3() )
}
